﻿#region Using Statements
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
#endregion

namespace GameBase.Sounds
{
    /// <summary>
    /// Defines properties and methods for management of playlist playback.
    /// </summary>
    public class PlayListManager : IPlayList, IDisposable
    {
        #region XACT Audio Fields

        // objects needed to implement XACT audio
        AudioEngine audioEngine;
        WaveBank waveBank;
        SoundBank soundBank;

        #endregion

        #region PlayList Fields & Properties

        // the playlist consists on a list of cue names to be played
        string[] playList;

        // index of the cue currently played
        int currentSongIndex = 0;

        // reference to the cue currently played
        Cue currentSongPlaying = null;

        /// <summary>
        /// Sets whether the playlist will be played back again.
        /// </summary>
        public bool Repeat
        {
            private get;
            set;
        }

        /// <summary>
        /// Gets whether playback of the playlist is already done.
        /// </summary>
        public bool Finish
        {
            get;
            private set;
        }

        #endregion

        #region Constructor

        /// <summary>
        /// Initializes a new instance of this class.
        /// </summary>
        /// <param name="contentPath">Path of XACT audio related content.</param>
        /// <param name="xactProjectName">XACT project file name - .xap</param>
        /// <param name="waveBankName">Wave bank name - .xwb</param>
        /// <param name="soundBankName">Sound bank name - .xsb</param>
        public PlayListManager(string contentPath, string xactProjectName, string waveBankName, string soundBankName)
        {
            // initialize XACT audio fields
            audioEngine = new AudioEngine(contentPath + xactProjectName + ".xgs");
            soundBank = new SoundBank(audioEngine, contentPath + soundBankName + ".xsb");

            // use a streaming wave bank... 
            // ... specify the packet size (size of the streaming buffer) in units of DVD sectors...
            // ... the optimal value is a multiple of 16 (1 DVD block = 16 DVD sectors)
            waveBank = new WaveBank(audioEngine, contentPath + waveBankName + ".xwb", 0, 16);
            // the audio engine must be updated before any streaming
            audioEngine.Update();

            // initialize playlist properties
            Repeat = false;
            Finish = false;
        }

        #endregion

        #region Dispose

        // dispose flag
        bool isDisposed = false;

        /// <summary>
        /// Immediately releases all resources used by this object.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
        }
        
        /// <summary>
        /// Releases the unmanaged resources used by this object and optionally releases the managed resources.
        /// </summary>
        /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
        protected virtual void Dispose(bool disposing)
        {
            if (!isDisposed)
            {
                if (disposing)
                {
                    // cleanup managed resources
                    if (audioEngine != null) { audioEngine.Dispose(); audioEngine = null; }
                    if (waveBank != null) { waveBank.Dispose(); waveBank = null; }
                    if (soundBank != null) { soundBank.Dispose(); soundBank = null; }

                    if (currentSongPlaying != null)
                    {
                        currentSongPlaying.Dispose();
                        currentSongPlaying = null;
                    }
                }

                isDisposed = true;
            }            
        }

        #endregion

        #region Update

        /// <summary>
        /// Performs periodic work required by the sound manager, and must be called once per frame.
        /// </summary>
        /// <param name="gameTime">Time passed since the last call to this method.</param>
        public virtual void Update(GameTime gameTime)
        {
            audioEngine.Update();

            // playlist not yet started
            if (currentSongPlaying == null)
                return;

            if (currentSongPlaying.IsPlaying)
            {
                if (isNext)
                {
                    // next song requested
                    isNext = false;
                    currentSongPlaying.Stop(AudioStopOptions.Immediate);
                    currentSongPlaying.Dispose();
                    currentSongPlaying = null;
                    GoToNext();
                }
                else if (isPrevious)
                {
                    // previous song requested
                    isPrevious = false;
                    currentSongPlaying.Stop(AudioStopOptions.Immediate);
                    currentSongPlaying.Dispose();
                    currentSongPlaying = null;
                    GoToPrevious();
                }
            }
            else if (!currentSongPlaying.IsPlaying && currentSongPlaying.IsStopped)
            {
                // current song finished... go to the next one
                currentSongPlaying.Dispose();
                currentSongPlaying = null;
                GoToNext();
            }
        }

        /// <summary>
        /// Moves to the next song in the playlist.
        /// </summary>
        void GoToNext()
        {
            currentSongIndex++;

            if (currentSongIndex == playList.Length)
            {
                Finish = true;

                if (Repeat)
                {
                    currentSongIndex = 0;
                    currentSongPlaying = soundBank.GetCue(playList[currentSongIndex]);
                    currentSongPlaying.Play();
                }
            }
            else
            {
                currentSongPlaying = soundBank.GetCue(playList[currentSongIndex]);
                currentSongPlaying.Play();
            }
        }

        /// <summary>
        /// Moves to the previous song in the playlist.
        /// </summary>
        void GoToPrevious()
        {
            currentSongIndex--;

            if (currentSongIndex == -1)
            {
                Finish = true;

                if (Repeat)
                {
                    currentSongIndex = playList.Length - 1;
                    currentSongPlaying = soundBank.GetCue(playList[currentSongIndex]);
                    currentSongPlaying.Play();
                }
            }
            else
            {
                currentSongPlaying = soundBank.GetCue(playList[currentSongIndex]);
                currentSongPlaying.Play();
            }
        }

        #endregion

        #region Start, Stop

        /// <summary>
        /// Starts playback of a playlist at a specific index.
        /// </summary>
        /// <param name="playList">List of cue friendly names to be played.
        /// <para>If the list is null or empty, no action will be performed.</para></param>
        /// <param name="startIndex">Index of the first cue to start from.
        /// <para>If no valid index provided, an index value of '0' will be used.</para></param>
        public void Start(string[] playList, int startIndex)
        {
            // checking arguments
            if (playList == null)
                return;
            if (playList.Length == 0)
                return;
            if (startIndex < 0 || startIndex >= playList.Length)
                startIndex = 0;

            // playlist already started
            if (currentSongPlaying != null)
                return;

            this.playList = playList;

            currentSongIndex = startIndex;
            currentSongPlaying = soundBank.GetCue(playList[startIndex]);
            currentSongPlaying.Play();
        }

        /// <summary>
        /// Starts playback of a playlist from the beginning.
        /// </summary>
        /// <param name="playList">List of cue friendly names to be played.</param>
        public void Start(string[] playList)
        {
            Start(playList, 0);
        }

        /// <summary>
        /// Stops playback of a playlist.
        /// <para>The playlist must have been started previously.</para>
        /// </summary>
        public void Stop()
        {
            if (currentSongPlaying == null)
                return;

            currentSongPlaying.Stop(AudioStopOptions.Immediate);
            currentSongPlaying = null;
        }

        #endregion

        #region Play, Pause

        /// <summary>
        /// Resumes playback of a paused playlist.
        /// <para>The playlist must have been started previously.</para>
        /// </summary>
        public void Play()
        {
            if (currentSongPlaying == null)
                return;

            if (currentSongPlaying.IsPrepared)
                currentSongPlaying.Play();
            else if (currentSongPlaying.IsPlaying && currentSongPlaying.IsPaused)
                currentSongPlaying.Resume();
        }

        /// <summary>
        /// Pauses playback of a playlist.
        /// <para>The playlist must have been started previously.</para>
        /// </summary>
        public void Pause()
        {
            if (currentSongPlaying == null)
                return;

            if (currentSongPlaying.IsPlaying && !currentSongPlaying.IsPaused)
                currentSongPlaying.Pause();
        }

        #endregion

        #region Next, Previous

        // next flag
        bool isNext = false;

        /// <summary>
        /// Moves to the next song in the playlist.
        /// <para>The playlist must have been started previously.</para>
        /// </summary>
        public void Next()
        {
            if (currentSongPlaying == null)
                return;

            isNext = true;
        }

        // previous flag
        bool isPrevious = false;

        /// <summary>
        /// Moves to the previous song in the playlist.
        /// <para>The playlist must have been started previously.</para>
        /// </summary>
        public void Previous()
        {
            if (currentSongPlaying == null)
                return;

            isPrevious = true;
        }

        #endregion

        #region Volume

        /// <summary>
        /// Sets the volume of all songs in the playlist.
        /// </summary>
        /// <param name="volume">Volume amount (0.0f -> silence - 1.0f -> full volume).</param>
        public void SetVolume(float volume)
        {
            // XACT project must have set a global variable of name "MusicVolume"
            audioEngine.SetGlobalVariable("Volume", volume);
        }

        #endregion
    }
}
